package com.gmrz.uap.common.db;

import io.shardingjdbc.core.api.ShardingDataSourceFactory;
import io.shardingjdbc.core.api.config.MasterSlaveRuleConfiguration;
import io.shardingjdbc.core.api.config.ShardingRuleConfiguration;
import io.shardingjdbc.core.api.config.TableRuleConfiguration;
import io.shardingjdbc.core.api.config.strategy.StandardShardingStrategyConfiguration;
import org.apache.log4j.Logger;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;

/**
 * 分库分表读写分离的数据源
 */
public class DataSourceJndiShardingAndMasterSlaveImpl implements DataSourceShardingFactory {

    private static final Logger LOG = Logger.getLogger(DataSourceJndiShardingAndMasterSlaveImpl.class);


    @Override
    public DataSource getInstance() throws SQLException {
        // 分片规则配置类-sharding-jdbc 入口
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        // 分片表规则配置 -- 认证数据表
        shardingRuleConfig.getTableRuleConfigs().add(getAuthenticatorsTableRuleConfiguration());
        // 分片表规则配置 -- 设备表
        shardingRuleConfig.getTableRuleConfigs().add(getDevicesTableRuleConfiguration());
        // 绑定需要进行分片的逻辑表，主要用来路由，它们的数据会根据配置的分片规则进行分库分表存储
        shardingRuleConfig.getBindingTableGroups().add(DataSourceShardingConst.SHARDING_TABLE_LIST);
        // 设置默认数据源，没有设置分片策略的表，它的数据都路由到默认数据库
        shardingRuleConfig.setDefaultDataSourceName(DataSourceShardingConst.getConfigInfo(DataSourceShardingConst.DATASOURCE_SHARDINGANDMASTERSLAVE_DEFAULT_JNDI));
        // 设置默认数据源分片策略，通过设置的“分片列”，及自定义的数据源分片策略选择“数据源”
        shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration(DataSourceShardingConst.SHARDING_KEY, ModuloShardingDatabaseAlgorithm.class.getName()));
        // 设置默认表分片策略，通过设置的“分片列”，及自定义的表分片策略选择“物理表”；逻辑表：t_devices，物理表：t_devices0 等
        shardingRuleConfig.setDefaultTableShardingStrategyConfig(new StandardShardingStrategyConfiguration(DataSourceShardingConst.SHARDING_KEY, ModuloShardingTableAlgorithm.class.getName()));
        // 主从配置，支持一主多从（由于进行了水平分库，每个库都可以有多个从库）
        shardingRuleConfig.setMasterSlaveRuleConfigs(getMasterSlaveRuleConfigurations());
        // 创建ShardingDataSource数据源
        return ShardingDataSourceFactory.createDataSource(createDataSourceMap(), shardingRuleConfig, new HashMap<String, Object>(), new Properties());
    }


    /**  t_authenticators 认证表分片的规则配置
     *
     *  new TableRuleConfiguration(// 逻辑表 "t_authenticators",  //数据源名.真实表 "uap_sharding_${0..1}.t_authenticators${[0，1]}" );
     *
     *  //数据源名.真实表  "uap_sharding_${0..1}.t_authenticators${[0,1,2]}"  实际数据节点；
     *  经过解析后得到：六个实际数据节点（t_authenticators逻辑表在两个库中物理存储表）
     *  uap_sharding_0.t_authenticators0、uap_sharding_0.t_authenticators1、uap_sharding_0.t_authenticators2
     *  uap_sharding_1.t_authenticators0、uap_sharding_1.t_authenticators1、uap_sharding_1.t_authenticators2
     * @return
     */
    private static TableRuleConfiguration getAuthenticatorsTableRuleConfiguration() {
        // 分片表规则配置类
        TableRuleConfiguration authenticatorsTableRuleConfig = new TableRuleConfiguration();
        // 将分片表名  赋给 逻辑表
        authenticatorsTableRuleConfig.setLogicTable(DataSourceShardingConst.SHARDING_TABLE_AUTHENTICATORS);
        // 设置分片表的规则
        String shardingDbTableRule = DataSourceShardingConst.getDatabaseTableShardingRule(DataSourceShardingConst.getConfigInfo(DataSourceShardingConst.DATASOURCE_SHARDINGANDMASTERSLAVE_NAME), DataSourceShardingConst.SHARDING_TABLE_AUTHENTICATORS);
        // 将设置的分片表的规则 赋给 “实际的数据节点”
        authenticatorsTableRuleConfig.setActualDataNodes(shardingDbTableRule);
        return authenticatorsTableRuleConfig;
    }

    /**
     *  t_devices 设备表分片的规则配置
     * @return
     */
    private static TableRuleConfiguration getDevicesTableRuleConfiguration() {
        TableRuleConfiguration devicesTableRuleConfig = new TableRuleConfiguration();
        devicesTableRuleConfig.setLogicTable(DataSourceShardingConst.SHARDING_TABLE_DEVICES);
        String shardingDbTableRule = DataSourceShardingConst.getDatabaseTableShardingRule(DataSourceShardingConst.getConfigInfo(DataSourceShardingConst.DATASOURCE_SHARDINGANDMASTERSLAVE_NAME), DataSourceShardingConst.SHARDING_TABLE_DEVICES);
        devicesTableRuleConfig.setActualDataNodes(shardingDbTableRule);
        return devicesTableRuleConfig;
    }

    /**
     * 读写分离的规则配置
     * @return
     */
    private static List<MasterSlaveRuleConfiguration> getMasterSlaveRuleConfigurations() {
        List<MasterSlaveRuleConfiguration> masterSlaveRuleConfigurationList = new ArrayList<MasterSlaveRuleConfiguration>();
        MasterSlaveRuleConfiguration masterSlaveRuleConfiguration;
        String masterDatabaseTemplate = "datasource.%s.master.jndi";
        String slaveDatabaseTemplate = "datasource.%s.slave.jndi.list";

        //读写分离配置名称前缀
        String masterSlaveNameTmp = DataSourceShardingConst.getConfigInfo(DataSourceShardingConst.DATASOURCE_SHARDINGANDMASTERSLAVE_NAME);

        //分库数量
        Integer shardingDatabaseCount = DataSourceShardingConst.getConfigInfoInt(DataSourceShardingConst.DATASOURCE_DATABASE_SHARDING_COUNT);

        for (int i = 0; i < shardingDatabaseCount; i++) {
            masterSlaveRuleConfiguration = new MasterSlaveRuleConfiguration();
            String masterSlaveName = masterSlaveNameTmp + i;//读写分离名称
            String masterJndi = String.format(masterDatabaseTemplate, masterSlaveName);//读写分离主数据库源
            String slaveJndi = String.format(slaveDatabaseTemplate, masterSlaveName);//读写分离从数据源

            masterSlaveRuleConfiguration.setName(masterSlaveName);
            masterSlaveRuleConfiguration.setMasterDataSourceName(DataSourceShardingConst.getConfigInfo(masterJndi));
            masterSlaveRuleConfiguration.setSlaveDataSourceNames(DataSourceShardingConst.getJndiListFromString(DataSourceShardingConst.getConfigInfo(slaveJndi)));

            masterSlaveRuleConfigurationList.add(masterSlaveRuleConfiguration);
        }

        return masterSlaveRuleConfigurationList;
    }

    private static Map<String, DataSource> createDataSourceMap() {

        String masterDatabaseTemplate = "datasource.%s.master.jndi";
        String slaveDatabaseTemplate = "datasource.%s.slave.jndi.list";

        //读写分离配置名称前缀
        String masterSlaveNameTmp = DataSourceShardingConst.getConfigInfo(DataSourceShardingConst.DATASOURCE_SHARDINGANDMASTERSLAVE_NAME);

        //分库数量
        Integer shardingDatabaseCount = DataSourceShardingConst.getConfigInfoInt(DataSourceShardingConst.DATASOURCE_DATABASE_SHARDING_COUNT);
        Map<String, DataSource> result = new HashMap<String, DataSource>();

        for (int i = 0; i < shardingDatabaseCount; i++) {
            String masterSlaveName = masterSlaveNameTmp + i;//读写分离名称
            String masterJndi = String.format(masterDatabaseTemplate, masterSlaveName);//读写分离主数据库源
            String slaveJndi = String.format(slaveDatabaseTemplate, masterSlaveName);//读写分离从数据源
            LOG.info("master jndi key ==> " + masterJndi);
            LOG.info("slave jndi key ==> " + slaveJndi);

            //主数据源
            LOG.info("master datasource ==> " + DataSourceShardingConst.getConfigInfo(masterJndi));
            result.put(DataSourceShardingConst.getConfigInfo(masterJndi), DataSourceShardingConst.getDataSourceForJndi(DataSourceShardingConst.getConfigInfo(masterJndi)));

            //从数据源列表
            String[] jndiList = DataSourceShardingConst.getJndiSharding(slaveJndi);

            for (String jndiName : jndiList) {
                LOG.info("slave datasource ==> " + jndiName);
                result.put(jndiName, DataSourceShardingConst.getDataSourceForJndi(jndiName));
            }
        }

        return result;
    }
}
